package cn.gx.kevin.dao.impl;

import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import cn.gx.kevin.common.thread.DaoDBKeyHolder;
import cn.gx.kevin.common.utils.DynamicDaoSupport;
import cn.gx.kevin.common.utils.DynamicParameter;
import cn.gx.kevin.common.utils.MyUUID;
import cn.gx.kevin.common.utils.PagingResult;
import cn.gx.kevin.dao.IBaseDao;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.session.RowBounds;
import org.apache.ibatis.session.SqlSession;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * @param <T> 对应的领域实体类型
 * @param <K> 主键数据类型
 * @author hjwen
 * @ClassName: BaseDao
 * @Description: dao增删改查基类
 * @date 2014-12-3 上午10:20:28
 */
public class BaseDao<T extends Serializable, K extends Serializable> extends DynamicDaoSupport
        implements IBaseDao<T, K> {

    public final Logger logger = LoggerFactory.getLogger(getClass());
    Method setIdMethod = null;
    Method getIdMethod = null;
    // mapper.xml中的namespace
    protected String namespace;
    // sqlmap.xml定义文件中对应的sqlid
    public static final String SQLID_INSERT = "insert";
    public static final String SQLID_INSERT_NOTNULL = "insertNotNull";
    public static final String SQLID_INSERT_BATCH = "insertBatch";
    public static final String SQLID_UPDATE = "update";
    public static final String SQLID_UPDATE_PARAM = "updateParam";
    public static final String SQLID_UPDATE_BATCH = "updateBatch";
    public static final String SQLID_DELETE = "delete";
    public static final String SQLID_DELETE_PARAM = "deleteParam";
    public static final String SQLID_DELETE_BATCH = "deleteBatch";
    public static final String SQLID_TRUNCATE = "truncate";
    public static final String SQLID_SELECT_PK = "selectPk";
    public static final String SQLID_SELECT_PARAM = "selectParam";
    public static final String SQLID_SELECT_MAP = "selectMap";
    public static final String SQLID_COUNT_PARAM = "countParam";
    public static final String SQLID_SELECT_ONFIELD = "selectOnfield";

    public Class<T> beanClass;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    /**
     * 切换到指定数据库
     ***/
    public void setDbKey(String key) {
        DaoDBKeyHolder.setKey(key);
    }

    /**
     * 恢复到默认数据库
     ***/
    public void clearDbKey() {
        DaoDBKeyHolder.clear();
    }


    public BaseDao() {
        Type type = getClass().getGenericSuperclass();
        if (type instanceof ParameterizedType) {
            ParameterizedType pType = (ParameterizedType) type;
            beanClass = (Class<T>) pType.getActualTypeArguments()[0];
            try {
                setIdMethod = beanClass.getMethod("setId", String.class);
                setIdMethod.setAccessible(true);
                getIdMethod = beanClass.getMethod("getId");
                getIdMethod.setAccessible(true);
            } catch (Exception e) {
                /* logger.info("{}",e); */
            }
            this.namespace = beanClass.getName();
        }
    }


    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#insertNotNull(java.io.Serializable)
     */
    @Override
    public int insertNotNull(T entity) throws Exception {
        return insertBean(entity, namespace + "." + SQLID_INSERT_NOTNULL);
    }

    private int insertBean(T entity, String sqlId) throws Exception {
        if (setIdMethod != null && getIdMethod != null) {
            Object value = getIdMethod.invoke(entity);
            if (value == null || StringUtils.isEmpty(String.valueOf(value))) {
                String id = MyUUID.snowflakeId();
                setIdMethod.invoke(entity, id);
            }
        }
        int rows = getSqlSession().insert(sqlId, entity);
        return rows;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#insert(java.io.Serializable)
     */
    @Override
    public int insert(T entity) throws Exception {
        return insertBean(entity, namespace + "." + SQLID_INSERT);
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#update(java.io.Serializable)
     */
    @Override
    public int update(T entity) throws Exception {
        int rows = getSqlSession().update(namespace + "." + SQLID_UPDATE, entity);
        return rows;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#updateParam(java.util.Map)
     */
    @Override
    public int updateParam(Map<String, Object> param) throws Exception {
        int rows = getSqlSession().update(namespace + "." + SQLID_UPDATE_PARAM, param);
        return rows;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#delete(java.io.Serializable)
     */
    @Override
    public int delete(K primaryKey) throws Exception {
        int rows = getSqlSession().delete(namespace + "." + SQLID_DELETE, primaryKey);
        return rows;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#deleteParam(java.util.Map)
     */
    @Override
    public int deleteParam(Map<String, Object> param) throws Exception {
        int rows = getSqlSession().delete(namespace + "." + SQLID_DELETE_PARAM, param);
        return rows;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#truncate()
     */
    @Override
    public int truncate() throws Exception {
        int rows = getSqlSession().delete(namespace + "." + SQLID_TRUNCATE);
        return rows;
    }

    @Override
    public Map<K, T> selectMap(Map<String, Object> param, String keyName) throws Exception {
        return selectMap(SQLID_SELECT_MAP, param, keyName);
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#count(java.util.Map)
     */
    @Override
    public int count(Map<String, Object> param) throws Exception {
        if (param == null) {
            return getSqlSession().selectOne(namespace + "." + SQLID_COUNT_PARAM);
        }
        int result = getSqlSession().selectOne(namespace + "." + SQLID_COUNT_PARAM, param);
        return result;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#get(java.io.Serializable)
     */
    @Override
    public T get(K primaryKey) throws Exception {
        return getSqlSession().selectOne(namespace + "." + SQLID_SELECT_PK, primaryKey);
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#selectAll()
     */
    @Override
    public List<T> selectAll() throws Exception {
        Map<String, Object> param = new HashMap<String, Object>();
        return this.selectParam(param);
    }

    /**
     * 根据参数里指定的字段、条件查询结果，用于指定只查询某些字段，以提高查询性能，走selectOnfield sql配置
     * List<String> selectFields 用户设置需要查询的字段，param.put("selectFields",selectFields);
     * 条件：whereFields: param.put("whereFields",List<whereFields>);
     *
     * @return 符合条件记录的实体对象的List
     */
    @Override
    public List<T> selectOnfield(Map<String, Object> param) throws Exception {
        List<T> list = this.selectListBySqlId(SQLID_SELECT_ONFIELD, param);
        if (list.size() == 1 && list.get(0) == null) {
            list.clear();
        }
        return list;
    }


    /**
     * 自定义返回类型E列表的查询
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @return
     * @throws Exception
     */
    protected <E> List<E> selectParamE(String sqlId, Map<String, Object> param) throws Exception {
        if (param == null) {
            return getSqlSession().selectList(namespace + "." + sqlId);
        }
        return getSqlSession().selectList(namespace + "." + sqlId, param);
    }

    protected String setSelectParamESqlId(Map<String, Object> param) {
        return "";
    }

    public <E> List<E> selectParamE(Map<String, Object> param) throws Exception {
        return this.selectParamE(setSelectParamESqlId(param), param);
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#selectParam(java.util.Map)
     */
    @Override
    public List<T> selectParam(Map<String, Object> param) throws Exception {
        // 补充排序条件
        this.makeOrderByCondition(param);
        String id = namespace + "." + SQLID_SELECT_PARAM;
        List<T> list = getSqlSession().selectList(id, param);
        return list;
        // 补充排序条件
        /*
         * if (param != null && param.containsKey("orderColumn")) { Object tmpObject =
         * param.get("orderColumn"); if (tmpObject instanceof Map) {
         * Set<Map.Entry<String, String>> keySet = ((Map<String,
         * String>)tmpObject).entrySet(); List<String> temp = new ArrayList<String>();
         * for (Map.Entry<String, String> entry : keySet) { temp.add(entry.getKey() +
         * " " + entry.getValue()); } param.remove("orderColumn");
         * param.put("orderColumn", temp); } } String id = namespace + "." +
         * SQLID_SELECT_PARAM; return getSqlSession().selectList(id, param);
         */
    }

    /**
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @return
     * @throws Exception
     */
    protected T selectBySqlId(String sqlId, Map<String, Object> param) throws Exception {
        if (param == null) {
            return getSqlSession().selectOne(namespace + "." + sqlId);
        }
        return getSqlSession().selectOne(namespace + "." + sqlId, param);
    }

    /**
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @param keyName
     * @return
     * @throws Exception
     */
    protected Map<K, T> selectMap(String sqlId, Map<String, Object> param, String keyName) throws Exception {
        Map<K, T> map;
        if (param == null)
            map = getSqlSession().selectMap(namespace + "." + sqlId, keyName);
        else {
            SqlSession session = getSqlSession();
            String key = namespace + "." + sqlId;
            map = session.selectMap(key, param, keyName);
        }
        return map;
    }

    /**
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @Description: 自定义返回类型E的单一值查询
     */
    protected <E> E selectOneE(String sqlId, Map<String, Object> param) {
        if (param == null) {
            return getSqlSession().selectOne(namespace + "." + sqlId);
        }
        return getSqlSession().selectOne(namespace + "." + sqlId, param);
    }

    /**
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @Description: 自定义返回类型E的单一值查询
     */
    protected T selectOne(String sqlId, Map<String, Object> param) {
        if (param == null) {
            return getSqlSession().selectOne(namespace + "." + sqlId);
        }
        return getSqlSession().selectOne(namespace + "." + sqlId, param);
    }


    /**
     * 自定义返回E,F类型Map的查询
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @return
     * @throws Exception
     */
    protected <E, F> Map<E, F> selectMapE(String sqlId, Map<String, Object> param, String mapKey) throws Exception {
        if (param == null) {
            return getSqlSession().selectMap(namespace + "." + sqlId, mapKey);
        }
        return getSqlSession().selectMap(namespace + "." + sqlId, param, mapKey);
    }

    /**
     * sqlId 注意不需要 加 namespace 前缀
     *
     * @param sqlId
     * @param param
     * @return
     * @throws Exception
     */
    protected List<T> selectListBySqlId(String sqlId, Map<String, Object> param) throws Exception {
        // 补充排序条件
        this.makeOrderByCondition(param);
        return getSqlSession().selectList(namespace + "." + sqlId, param);
    }

    private void makeOrderByCondition(Map<String, Object> param) {
        if (param != null && param.containsKey("orderColumn")) {
            Object tmpObject = param.get("orderColumn");
            boolean isMap = tmpObject instanceof Map;
            if (isMap) {
                Set<Map.Entry<String, String>> keySet = ((Map<String, String>) tmpObject).entrySet();
                List<String> temp = new ArrayList<String>();
                for (Map.Entry<String, String> entry : keySet) {
                    temp.add(entry.getKey() + " " + entry.getValue());
                }
                param.remove("orderColumn");
                param.put("orderColumn", temp);
            }
        }
    }

    private PaginSize dealPaginParams(DynamicParameter dynamicParameter){
        PaginSize paginSize = new PaginSize();
        int page = dynamicParameter.getPage() == null || "".equals(dynamicParameter.getPage()) ? 1
                : dynamicParameter.getPage(); // 默认为第一页
        int size = dynamicParameter.getSize() == null || "".equals(dynamicParameter.getSize()) ? 15
                : dynamicParameter.getSize();

        RowBounds rowBounds = new RowBounds((page - 1) * size, size);
        Map<String, Object> param = dynamicParameter.getParams();
        List<String> orderString = dynamicParameter.getOrderColumn();
        if (param == null) {
            param = new HashMap<String, Object>();
        }
        if (orderString.size() > 0) {
            param.put("orderColumn", orderString);
        }
        paginSize.setPage(page);
        paginSize.setPageSize(size);
        paginSize.setParams(param);
        paginSize.setRowBounds(rowBounds);
        return paginSize;
    }

    protected <E> PagingResult<E> selectPaginationE(DynamicParameter dynamicParameter, String sqlID) throws Exception {
        PaginSize paginSize = dealPaginParams(dynamicParameter);
        String nameSpaceSql = namespace + "." + sqlID;
        PageHelper.startPage(paginSize.getPage(), paginSize.getPageSize());
        List<E> resultList = getSqlSession().selectList(nameSpaceSql, paginSize.getParams(), paginSize.getRowBounds());
        PageInfo<E> pageResult = new PageInfo<E>(resultList);
        long totalSize = pageResult.getTotal();
        PagingResult<E> pagingResult = new PagingResult<E>();
        pagingResult.setCurrentPage(paginSize.getPage());
        pagingResult.setTotalSize(totalSize);
        List<E> lise = pageResult.getList();
        pagingResult.setResultList(lise);
        return pagingResult;
    }


    /*
     * (non-Javadoc)
     *
     * @see
     * IBaseDao#selectPagination(kevin.framework.basic.
     * common.utils.DynamicParameter)
     */
    @Override
    public PagingResult<T> selectPagination(DynamicParameter dynamicParameter) throws Exception {
        PaginSize paginSize = dealPaginParams(dynamicParameter);
        String nameSpaceSql = this.setPaginationSql(dynamicParameter);
        PageHelper.startPage(paginSize.getPage(), paginSize.getPageSize());
        List<T> resultList = getSqlSession().selectList(nameSpaceSql, paginSize.getParams(), paginSize.getRowBounds());
        PageInfo<T> pageResult = new PageInfo<T>(resultList);
        long totalSize = pageResult.getTotal();
        PagingResult<T> pagingResult = new PagingResult<T>();
        pagingResult.setCurrentPage(paginSize.getPage());
        pagingResult.setTotalSize(totalSize);
        List<T> lise = pageResult.getList();
        pagingResult.setResultList(lise);
        return pagingResult;
    }

    /***
     * 分页sql 设置 默认 SQLID_SELECT_PARAM
     * **/
    protected String setPaginationSql(DynamicParameter dynamicParameter) {
        String nameSpaceSql = namespace + "." + SQLID_SELECT_PARAM;
        return nameSpaceSql;
    }


    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#insertBatch(java.util.List)
     */
    @Override
    public int insertBatch(List<T> list) throws Exception {
        SqlSession session = getSqlSession();
        Map<String, Object> params = new HashMap<>();
        String dbType = this.getDbType();
        params.put("list", list);
        params.put("dbType", dbType);
        int res = session.insert(namespace + "." + SQLID_INSERT_BATCH, params);
        return res;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#updateBatch(java.util.List)
     */
    @Override
    public int updateBatch(List<T> list) throws Exception {
        int rows = 0;
        for (T t : list) {
            rows = rows + getSqlSession().update(namespace + "." + SQLID_UPDATE_BATCH, t);
        }
        return rows;
    }

    /*
     * (non-Javadoc)
     *
     * @see IBaseDao#deleteBatch(java.util.List)
     */
    @Override
    public int deleteBatch(List<K> list) throws Exception {
        if (list.size() == 1) {
            return this.delete(list.get(0));
        }
        return getSqlSession().delete(namespace + "." + SQLID_DELETE_BATCH, list);
    }

    @Override
    public List<T> selectTop(int top, Map<String, Object> params, LinkedHashMap<String, String> orderMap) throws Exception {
        if (params == null) {
            params = new HashMap<>();
        }
        RowBounds rowBounds = new RowBounds(0, top);
        String nameSpaceSql = namespace + "." + SQLID_SELECT_PARAM;
        PageHelper.startPage(1, top);
        if (orderMap != null) {
            List<String> temp = new ArrayList<String>();
            for (Map.Entry<String, String> entry : orderMap.entrySet()) {
                String key = entry.getKey().replaceAll(".*([';]+|(--)+).*", "").replaceAll("<", "&lt;")
                        .replaceAll(">", "&gt;").replaceAll("drop|delete|truncate|exec", "");
                String val = entry.getValue().toLowerCase();
                if (!(val.equals("desc") || val.equals("asc"))) {
                    val = "asc";
                }
                if (StringUtils.isNoneEmpty(key) && StringUtils.isNoneEmpty(val)) {
                    temp.add(key + " " + val);
                }
            }
            params.put("orderColumn", temp);
        }
        params.put("_unsqlcount", true);
        List<T> resultList = getSqlSession().selectList(nameSpaceSql, params, rowBounds);
        return resultList;
    }


    class PaginSize {
        int page;
        int pageSize;

        RowBounds rowBounds;

        Map<String,Object> params;

        public RowBounds getRowBounds() {
            return rowBounds;
        }

        public void setRowBounds(RowBounds rowBounds) {
            this.rowBounds = rowBounds;
        }

        public Map<String, Object> getParams() {
            return params;
        }

        public void setParams(Map<String, Object> params) {
            this.params = params;
        }

        public int getPage() {
            return page;
        }

        public void setPage(int page) {
            this.page = page;
        }

        public int getPageSize() {
            return pageSize;
        }

        public void setPageSize(int pageSize) {
            this.pageSize = pageSize;
        }
    }
}
